package lol.clann.bukkit.pluginboot.util;

import lol.clann.bukkit.pluginboot.model.IntPos3;
import lol.clann.clannboot.annotation.svc.PostInstance;
import lol.clann.clannboot.annotation.svc.Service;
import lol.clann.clannboot.util.clazz.ClassUtils;
import lol.clann.clannboot.util.clazz.FieldFilter;
import lol.clann.clannboot.util.clazz.WrapClass;
import lol.clann.clannboot.util.clazz.WrapField;
import org.bukkit.World;
import org.bukkit.block.Block;

/**
 * @author pyz
 * @date 2018/10/7 下午6:48
 */
@Service(priority = Integer.MAX_VALUE)
public class MovingObjectPositionUtils {

    public static final WrapClass classMovingObjectPosition = ClassUtils.forName("{nms}.MovingObjectPosition");
    public static final WrapClass classEnumMovingObjectType = ClassUtils.forName("{nms}.EnumMovingObjectType", "{nms}.MovingObjectPosition$EnumMovingObjectType");

    private boolean useBlockPos = false;

    // 高版本
    private WrapClass classBlockPosition;
    private WrapClass classBaseBlockPosition;
    private WrapClass classEnumDirection;
    private WrapField<Integer> blockX;
    private WrapField<Integer> blockY;
    private WrapField<Integer> blockZ;
    private WrapField<Integer> sideHit;
    private WrapField direction;
    private WrapField blockPos;
    private WrapField hitType;

    @PostInstance
    private void init() {
        try {
            classBlockPosition = ClassUtils.forName("{nms}.BlockPosition");
            classBaseBlockPosition = ClassUtils.forName("{nms}.BaseBlockPosition");
            classEnumDirection = ClassUtils.forName("{nms}.EnumDirection");
            blockX = classBaseBlockPosition.getField(FieldFilter.build().type(int.class).index(0));
            blockY = classBaseBlockPosition.getField(FieldFilter.build().type(int.class).index(1));
            blockZ = classBaseBlockPosition.getField(FieldFilter.build().type(int.class).index(2));
            direction = classMovingObjectPosition.getField(FieldFilter.build().type(classEnumDirection));
            blockPos = classMovingObjectPosition.getField(FieldFilter.build().type(classBlockPosition));
            useBlockPos = true;
        } catch (Throwable e) {
            blockX = classMovingObjectPosition.getField(FieldFilter.build().name("blockX"));
            blockY = classMovingObjectPosition.getField(FieldFilter.build().name("blockY"));
            blockZ = classMovingObjectPosition.getField(FieldFilter.build().name("blockZ"));
            sideHit = classMovingObjectPosition.getField(FieldFilter.build().name("sideHit"));
            useBlockPos = false;
        }
        hitType = classMovingObjectPosition.getField(FieldFilter.build().type(classEnumMovingObjectType));
    }

    public Block getBlock(World world, Object movingObjectPosition) {
        if (isMiss(movingObjectPosition) || !isHitBlock(movingObjectPosition)) {
            return null;
        }
        IntPos3 pos = getPos(movingObjectPosition);
        return world.getBlockAt(pos.getX(), pos.getY(), pos.getZ());
    }

    public Block getNearBlock(World world, Object movingObjectPosition) {
        if (isMiss(movingObjectPosition) || !isHitBlock(movingObjectPosition)) {
            return null;
        }
        IntPos3 pos = getPos(movingObjectPosition);
        Offset offset = getOffset(movingObjectPosition);
        return world.getBlockAt(pos.getX() + offset.offsetX, pos.getY() + offset.offsetY, pos.getZ() + offset.offsetZ);
    }

    public IntPos3 getPos(Object movingObjectPosition) {
        if (useBlockPos) {
            Object pos = blockPos.get(movingObjectPosition);
            return new IntPos3().setX(blockX.get(pos)).setY(blockY.get(pos)).setZ(blockZ.get(pos));
        } else {
            return new IntPos3().setX(blockX.get(movingObjectPosition)).setY(blockY.get(movingObjectPosition)).setZ(blockZ.get(movingObjectPosition));
        }
    }

    public Enum getHitType(Object movingObjectPosition) {
        return (Enum) hitType.get(movingObjectPosition);
    }

    public boolean isHitBlock(Object movingObjectPosition) {
        Enum hitType = getHitType(movingObjectPosition);
        return hitType.ordinal() == 1;
    }

    public boolean isHitEntity(Object movingObjectPosition) {
        Enum hitType = getHitType(movingObjectPosition);
        return hitType.ordinal() == 2;
    }

    public boolean isMiss(Object movingObjectPosition) {
        Enum hitType = getHitType(movingObjectPosition);
        return hitType.ordinal() == 0;
    }

    /**
     * 通过点击面获取偏移
     *
     * @return
     */
    public Offset getOffset(Object movingObjectPosition) {
        int side = -1;
        if (useBlockPos) {
            Enum sideHit = (Enum) direction.get(movingObjectPosition);
            side = sideHit.ordinal();
        } else {
            side = sideHit.get(movingObjectPosition);
        }
        return Offset.values()[side];
    }

    public static enum Offset {
        DOWN(0, -1, 0),
        UP(0, 1, 0),
        NORTH(0, 0, -1),
        SOUTH(0, 0, 1),
        WEST(-1, 0, 0),
        EAST(1, 0, 0),
        UNKNOWN(0, 0, 0);

        private final int offsetX;
        private final int offsetY;
        private final int offsetZ;

        Offset(int offsetX, int offsetY, int offsetZ) {
            this.offsetX = offsetX;
            this.offsetY = offsetY;
            this.offsetZ = offsetZ;
        }

        public int getOffsetX() {
            return offsetX;
        }

        public int getOffsetY() {
            return offsetY;
        }

        public int getOffsetZ() {
            return offsetZ;
        }
    }
}
